package flca.voicenav.util;

import java.util.ArrayList;
import java.util.List;

import com.google.android.maps.GeoPoint;

import flca.voicenav.VoiceNavConstants;
import flca.voicenav.model.Dir;
import flca.voicenav.model.GpxRoute;
import flca.voicenav.model.TracePoints;
import flca.voicenav.model.TracePt;


/**
 * This helper class calculates distance to next point etc
 *
 */
public class TracePtHelper implements VoiceNavConstants
{

	public static void processLocation(TracePoints aTracePoints, GpxRoute aGpxRoute)
	{
		int currIndex = aTracePoints.prev().getSegmIndex();
		if (currIndex < 0) {
			return;//this should only happen when starting
		}
		
		int segmIndex = findCorrespondingSegment(aTracePoints, aGpxRoute);
		
		if (segmIndex >= 0) {
			Values values = calcValues(segmIndex, aTracePoints, aGpxRoute);
			updateValues(aTracePoints.last(), values);
			values.dir = isValidDirection(aTracePoints, aGpxRoute, values);
			aTracePoints.last().setDirection(values.dir);
		} 
	}
	
	public static boolean isLastPointValid(TracePoints aTracePoints)
	{
		return Dir.UP == aTracePoints.last().getDirection() || Dir.DOWN == aTracePoints.last().getDirection();
	}
	
	public static boolean nearTheEnd(TracePoints aTracePoints, GpxRoute aGpxRoute, int aMinDist)
	{
		TracePt lastPt = aTracePoints.last();
		
		if (Dir.UP == lastPt.getDirection()) {
			return 	lastPt.getNextCorner() == aGpxRoute.lastCorner() &&
					lastPt.getNextDist() < aMinDist;
		} else {
			return 	lastPt.getNextCorner() == aGpxRoute.firstCorner() &&
					lastPt.getNextDist() < aMinDist;
		}
	}

	public static double getDistanceToFinish(GeoPoint aPoint, GpxRoute aGpxRoute, Dir aDir) 
	{
		double distToEnd = DistHelper.calcDistance(aPoint, aGpxRoute.getTrkPt(aGpxRoute.size() -1).toGeoPoint());
		double distToStart = DistHelper.calcDistance(aPoint, aGpxRoute.getTrkPt(0).toGeoPoint());
		if (Dir.DOWN == aDir) {
			return distToStart;
		} else {
			return distToEnd;
		}
	}
	
	/**
	 * Hier berekenen we alle waardes die van belang zijn om het beste segment te vinden
	 * @param aSegmIdx
	 * @param aTracePoints
	 * @param aGpxRoute
	 * @return
	 */
	private static Values calcValues(int aSegmIdx, TracePoints aTracePoints, GpxRoute aGpxRoute)
	{
		Values r = new Values();

		if (aSegmIdx >= aGpxRoute.size() ) {
			System.out.println("");
		}
		r.segmIndex = aSegmIdx;
		r.nextCorner = aGpxRoute.getNextCorner(aSegmIdx);
		r.prevCorner = aGpxRoute.getPrevCorner(aSegmIdx);
		GeoPoint p1 = aGpxRoute.getCoordinates().get(r.nextCorner).toGeoPoint();
		GeoPoint p2 = aGpxRoute.getCoordinates().get(r.prevCorner).toGeoPoint();
		GeoPoint lastloc = aTracePoints.last().geoPoint();
		r.nextDist = (int) DistHelper.getDistance(p1, lastloc);
		r.prevDist = (int) DistHelper.getDistance(p2, lastloc);		
		r.segmentPoint = DistHelper.getClosestPointOnSegment(p1, p2, lastloc);
		r.distToSegm = (int) DistHelper.getDistance(r.segmentPoint, lastloc);
		r.onSegment = isPointOnSegment(aSegmIdx, lastloc, aGpxRoute);
		
		return r;
	}

	private static void updateValues(TracePt aTracePt, Values aValues)
	{
		aTracePt.setSegmIndex(aValues.segmIndex);
		aTracePt.setMinDist(aValues.distToSegm);
		aTracePt.setNextDist(aValues.nextDist);
		aTracePt.setPrevDist(aValues.nextDist);
		aTracePt.setNextCorner(aValues.nextCorner);
		aTracePt.setPrevCorner(aValues.prevCorner);
		aTracePt.setSegmIndex(aValues.segmIndex);
		aTracePt.setDirection(aValues.dir);
	}

	private static Dir isValidDirection(TracePoints aTracePoints, GpxRoute aGpxRoute, Values aValues)
	{
		if (gettingCloserToNextCoord(aTracePoints, aValues)) {
			return Dir.UP;
		} else if (gettingCloserToPrevCoord(aTracePoints, aValues)) {
			return Dir.DOWN;
		} else if (aValues.distToSegm < VoiceNavConstants.MIN_NAVPNT_DIST) {
			Dir dir = aTracePoints.prev().getDirection();
			return (dir != null) ? dir : Dir.UP; //assume UP when starting 
		} else {
			return Dir.UNKNOWN;
		}
	}

	private static final boolean OPTIMIZED = true;
	private static final boolean BRUTE_FORCE = false;

	/*
	 * find the segment where the project point is closest.
	 * we start with the prev segm and continue until the distance to the segment get worse.
	 */
	private static int findCorrespondingSegment(TracePoints aTracePoints, GpxRoute aGpxRoute)
	{
		int foundAt = -1;
		int prevSegm = aTracePoints.prev() != null ? aTracePoints.prev().getSegmIndex() : 0;
		
		List<Values> values = findTheCorrespondingSegment(aTracePoints, aGpxRoute, prevSegm, OPTIMIZED);
		foundAt = getBestSegment(values, aTracePoints.last().geoPoint(), aGpxRoute);

		if (foundAt < 0) {
			values = findTheCorrespondingSegment(aTracePoints, aGpxRoute, 0, BRUTE_FORCE);
			return getBestSegment(values, aTracePoints.last().geoPoint(), aGpxRoute);
		} else {
			return foundAt;
		}
	}

	private static List<Values> findTheCorrespondingSegment(TracePoints aTracePoints, GpxRoute aGpxRoute, int prevSegm, boolean aOptimized)
	{
		List<Values> values = new ArrayList<Values>();
		for (int i=prevSegm; i < aGpxRoute.size(); i++) {
			values.add(calcValues(i, aTracePoints, aGpxRoute));
			if (aOptimized && stopLooking(values)) {
				break;
			}
		}
		return values;
	}

	private static boolean stopLooking(List<Values> aValues)
	{
		if (aValues.size() < 2) {
			return false; //we should at least look at the next segm
		} else {
			return disToSegmentGetWorse(aValues);
		}
	}

	private static int getBestSegment(List<Values> aValues, GeoPoint aLastPoint, GpxRoute aGpxRoute)
	{
		double mindist = -1.0;
		int foundAt = -1;
		for (Values  v : aValues) {
			if (isPointOnSegment(v.segmIndex, aLastPoint, aGpxRoute)) {
				if (mindist < 0 || v.distToSegm < mindist) {
					mindist = v.distToSegm;
					foundAt = v.segmIndex;
				}
			}
		} 
		return foundAt;
	}
	
	private static boolean disToSegmentGetWorse(List<Values> aValues) 
	{
		return aValues.get(aValues.size()-1).distToSegm > aValues.get(aValues.size()-2).distToSegm;
	}
	
//	private static double getDistToSegment(int i, GeoPoint aLastPoint, GpxRoute aGpxRoute) 
//	{
//		GeoPoint segmPoints[] = aGpxRoute.getSegmentPoints(i);
//		return DistHelper.getDistanceToSegment(segmPoints[0], segmPoints[1], aLastPoint);
//	}

	private static boolean isPointOnSegment(int i, GeoPoint aLastPoint, GpxRoute aGpxRoute) 
	{
		GeoPoint segmPoints[] = aGpxRoute.getSegmentPoints(i);
		return DistHelper.isPointOnSegment(segmPoints[0], segmPoints[1], aLastPoint);
	}
	
	private static boolean gettingCloserToNextCoord(TracePoints aTracePoints, Values aValues) 
	{
		if (aTracePoints.prev() != null) {
			return aValues.nextDist < aTracePoints.prev().getNextDist(); 
		} else {
			return false;
		}
	}
	
	private static boolean gettingCloserToPrevCoord(TracePoints aTracePoints, Values aValues) 
	{
		if (aTracePoints.prev() != null) {
			return aValues.prevDist < aTracePoints.prev().getPrevDist(); 
		} else {
			return false;
		}
	}
	
	//--- helper class to temp keep calculated values
	static class Values {
		int segmIndex;
		int nextCorner, prevCorner;
		int nextDist, prevDist, distToSegm;
		GeoPoint segmentPoint;
		boolean onSegment; 
		Dir dir;

		@Override
		public String toString()
		{
			return "Values [segmIndex=" + segmIndex + ", distToSegm=" + distToSegm + ", onSegment=" + onSegment + ", nextCorner=" + nextCorner + ", prevCorner=" + prevCorner + ", nextDist="
					+ nextDist + ", prevDist=" + prevDist + "]";
		}
	}

	//----------- shortcuts ------------
}
